package game.service;

import com.sghd.common.utils.ManagedException;
import com.sghd.common.utils.array.ArrayHelper;
import com.sghd.common.utils.json.JsonUtils;
import data.model.PropType;
import data.service.DataService;
import game.dic.Status;
import game.dic.TishiManager;
import game.model.*;
import game.rule.Rule;
import io.netty.channel.Channel;
import network.ServerHandler;
import network.SocketEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @author fank
 * @date 2018-08-18 14:22
 * @desc
 **/
public class DeskService {
    private Logger logger = LoggerFactory.getLogger(getClass());
    private int num = -1;
    private PlayerService playerService;
    private DataService dataService;
    private RoomService roomService;
    private int state = Status.NORMAL;
    private ConfigRoom configRoom;
    private GameService gameService;
    /** 小游戏所需最小连击次数*/
    private int miniCombo = -1;
    /** 下注索引集合*/
    private Set<Integer> betIndexs = new HashSet<>();
    /** 共享锁*/
    public byte[] lock = new byte[0];
    /** 超时*/
    private int timeout = 180000;

    public DeskService(int num, DataService dataService, RoomService roomService) {
        this.num = num;
        this.dataService = dataService;
        this.roomService = roomService;
        this.configRoom = roomService.getConfigRoom();
        this.gameService = roomService.getGameService();
        miniCombo = configRoom.getMgame().keySet().stream().sorted().findFirst().get();
        this.configRoom = roomService.getConfigRoom();
        for (int i = 0; i < configRoom.getBetArea().length; i++) {
            betIndexs.add(i);
        }
        this.playerService = null;
        addStatus(Status.IDEL);
        scheduler.start();//开启线程
    }

    /**
     * 玩家离开桌子
     * @param playerId
     * @return
     */
    public void leaveDesk(long playerId) {
        try {
            long current = playerService.getPlayerId();
            if (current != playerId) {
                //该玩家不在当前桌子上
                return;
            }
            //离开桌子记录
            dataService.leaveDesk(playerId, roomService.getConfigRoom().getGameId(), roomService.getConfigRoom().getRoomId(),
                    num, 0, 0);
            playerService.exit(dataService);
        } catch (Exception e) {
            logger.error("玩家离开桌子发生未知错误！", e);
        } finally {
            this.playerService = null;
            this.state = Status.NORMAL;
            addStatus(Status.IDEL);//当前桌子处理空闲状态
        }
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    public int getState() {
        return state;
    }

    /**
     * 当前桌子是否是空闲的
     * @return
     */
    public boolean isIdle() {
        return playerService == null && hasStatus(Status.IDEL);
    }


    public void addStatus(int state) {
        if (hasStatus(state)) {
            return;
        }
        this.state |= state;
    }

    public boolean hasStatus(int state) {
        return (this.state & state) == state;
    }

    public void removeStatus(int state) {
        if (hasStatus(state)) {
            this.state ^= state;
        }
    }

    public InitDesk joinDesk(PlayerService playerService) throws Exception {
        if (this.playerService != null) {
            throw new RuntimeException("该桌子已有玩家存在！");
        }
        //初始化玩家，记录(免费，小游戏)
        playerService.join(dataService);
        DeskPlayer player = playerService.getPlayer();
        List<MGame> mgames = player.getMgames();
        if (mgames != null && !mgames.isEmpty()) {
            //小游戏未结束, 进入小游戏状态
            addStatus(Status.MGAME);
        }
        ConfigRoom config = roomService.getConfigRoom();
        synchronized (lock) {
            this.playerService = playerService;
            lock.notify();//通知线程开始工作
        }
        playerService.setDeskId(num);
        InitDesk initDesk = new InitDesk(config.getBetArea(), player.getMondey(PropType.GOLD), player.getNickName(),
                player.getVipLevel(),
                player.getHeadUrl(), playerService.getFreeCount(), config.getFreeCount(),
                playerService.getDeskPlayer().getFreeMoney(), playerService.getCombo() == miniCombo - 1,
                playerService.getBuyTimes(), playerService.getBetIndex(), configRoom.getBuy2Bet(), mgames);
        removeStatus(Status.IDEL);//标记为非空闲状态

        return initDesk;
    }

    /**
     * 开始游戏
     * @param playerService
     * @param betIdx
     * @return
     */
    public GameResult begin(PlayerService playerService, int betIdx) {
        if (playerService != this.playerService) {
            //当前玩家不在请放心桌子上
            throw new ManagedException(-1, "当前玩家不在该桌子上");
        }
        //当前是否是免费
        boolean cfree = playerService.getDeskPlayer().getFreeCount() > 0;
        canGame(playerService, cfree, betIdx);
        addStatus(Status.ROTATE);//开始旋转
        boolean buy = false;
        if (cfree || betIdx == -1) {
            if (betIdx == -1) {
                buy = true;
            }
            betIdx = playerService.getBetIndex();
        }
        int baseBet = roomService.getConfigRoom().getBetArea()[betIdx];
        int cost = 0;
        int combo = 0;
        //计算购买花费
        if (buy) {
            combo = playerService.getCombo();//上一轮的连击次数
            int buyTimes = playerService.getBuyTimes();
            cost = configRoom.getBuy2Bet().get(buyTimes + 1) * baseBet * Rule.getSize();//本次购买花费
            playerService.setBuyTimes(playerService.getBuyTimes() + 1);//增加购买次数
        }
        GameResult gameResult = playerService.begin(cfree, cost, baseBet, roomService.getRefund(betIdx),
                configRoom.getRefundCtrl().get(betIdx), configRoom.getRefund());//旋转
        if (!cfree && betIdx != -1) {
            playerService.setBetIndex(betIdx);//保存本轮下注信息
        }
        //计算小游戏,根据连击次数，判断小游戏次数
        combo += gameResult.getCombo();
        int mgame = configRoom.getMGame(combo);
        //进入小游戏
        if (mgame > 0) {
            logger.debug("进入小游戏");
            mGame(playerService, mgame, gameResult, betIdx);
        } else {
            playerService.setCombo(combo);//如果未中小游戏，保存本局连击
        }
        //购买上限
        int buyLimit = configRoom.getBuy2Bet().keySet().stream().sorted((o1, o2) -> -1).findFirst().get();
        //如果玩家购买成功(通过购买成功进入小游戏) 或者购买达到上限后不允许再购买
        if (mgame > 0 || playerService.getBuyTimes() >= buyLimit) {
            playerService.setCombo(0);//连击次数清零
            playerService.setBuyTimes(0);//购买次数清零
        }
        //免费中，获得的金币总数
        playerService.setFreeMoney(gameResult.getTotalFen(), cfree);
        //金币变更值
        long change = gameResult.getTotalFen() - (buy ? gameResult.getCost() : gameResult.getTotalBet());
        if (logger.isDebugEnabled()) {
            logger.debug("玩家当前金币：{}", playerService.getMoney(PropType.GOLD));
            logger.debug("总压注：{}, 是否是免费：{}", gameResult.getTotalBet(), cfree);
            logger.debug("是否够买连击服务：{}, 购买花费：{}", buy, gameResult.getCost());
            logger.debug("获得金币：{}", change);
            logger.debug("最终金币：{}", playerService.getMoney(PropType.GOLD) + change);
        }
        gameResult.setMoney(playerService.getMoney(PropType.GOLD) + change);//玩家最终金币
        //中奖倍数
        double bet = gameResult.getTotalFen() * 1.0 / (baseBet * Rule.getSize());
        //中奖等级
        gameResult.setRewardType(ArrayHelper.getRange(bet, configRoom.getRewardRang()));
        playerService.sendMessage(SocketEnum.gameResult, gameResult);//发送游戏结果
        //本次数据结算
        try {
//            dataService.settleNormal(playerService.getDeskPlayer(), configRoom.getGameId(), configRoom.getRoomId(),
//                    num, gameResult, change);
            playerService.change(change);
            roomService.rebeat((long) (buy ? gameResult.getCost() : gameResult.getTotalBet() * configRoom.getDrawWaters()));
            if (cfree) {
                playerService.decressFree();//扣除免费旋转次数
            }
        } catch (Exception e) {
            logger.error("玩家[{}]结算失败！", e);
            dataService.settleError(playerService.getDeskPlayer(), gameResult, e);
            playerService.sendMessage(SocketEnum.infoI, TishiManager.getTishiT("结算异常", TishiManager.WINDOW_F,
                    null));
        }
        removeStatus(Status.ROTATE);
        if (mgame == 0) {
            playerService.sendMessage(SocketEnum.finishI, "");//服务器结算完成
        }
        return gameResult;
    }

    /**
     * 生成小游戏结果
     * @param playerService
     * @param betIdx
     * @param totalBet
     * @param gameResult
     */
    private void mGame(PlayerService playerService, int mgame, GameResult gameResult, int betIndex) {
        addStatus(Status.MGAME);
        List<MGame> games = new ArrayList<>();
        long totalBet = configRoom.getBetArea()[betIndex] * Rule.getSize();
        for (int i = 0; i < mgame; i++) {//多轮小游戏结果
            //小游戏
            List<MType> mgames = getMgame(betIndex);
            Map<MType, Integer> counter = new HashMap<>();
            long mfen = 0;//小游戏总得分
            float scope = 0f;//花或者飞镖的属性值
            int end = 0;
            MGame game = new MGame(totalBet);
            for (MType mType : mgames) {
                end++;
                if (mType.isGold()) {//金币得分
                    mfen += totalBet * mType.getScoper();
                    continue;
                }
                int count = counter.get(mType) == null ? 1 : counter.get(mType) + 1;
                if (count == 3) {//找到小游戏结果
                    //TODO 反分池受限，取最低数值
                    scope = configRoom.getScope(mType);//获取花或者飞镖的属性值
                    if (mType == MType.FLOWER) {//花得分
                        mfen += totalBet * scope;
                    }
                    game.update(mType, scope, mfen, mgames.subList(0, end));
                    break;
                }
                counter.put(mType, count);
            }
            games.add(game);
            gameResult.addMlist(game.toVo());//
            //更新反分池
            logger.debug("小游戏之前反分池金币：" + roomService.getRefund(betIndex).get());
            roomService.getRefund(betIndex).addAndGet(-game.getTotalFen());
            logger.debug("小游戏完成后反分池金币：" + roomService.getRefund(betIndex).get());
        }
        playerService.record(games);//记录生成的小游戏结果
        logger.debug("生成小游戏结果 ：{}", JsonUtils.object2String(games));
    }

    public List<MType> getMgame(int betIndex) {
        AtomicLong refund = roomService.getRefund(betIndex);
        int base = configRoom.getBetArea()[betIndex];
        List<MType> resuts = MType.get();
        //反分池对小游戏的控制
        if (refund.get() < configRoom.getRefundCtrl().get(betIndex)[0] * base * Rule.getSize()) {
            List<MType> tmp = new ArrayList<>();
            resuts.forEach(mType -> {
                if (mType == MType.FLOWER || mType == MType.DART || mType == MType.GLOD_HALF) {
                    tmp.add(mType);
                }
            });
            Collections.shuffle(tmp);
            resuts.removeAll(tmp);
            resuts.addAll(0, tmp);
        }
        return resuts;
    }

    /**
     * 玩家选择小游戏格子索引
     *
     * */
    public void select(PlayerService playerService, int index) {
        if (!hasStatus(Status.MGAME)) {
            //没有正在进行中的小游戏
            return;
        }
        MGame result = playerService.getMgame();
        if (result == null || result.getSequen().isEmpty()) {
            //没有正在进行中的小游戏
            throw new ManagedException(-1, "没有正在进行中的小游戏！");
        }
        //为什么不是0-1516个格子？因为还有一个特殊索引16代表玩家，已经翻开了一朵花，本次小游戏结束
        if (index < -1 || index > 15) {
            throw new ManagedException(-1, "选择了一个非法的索引");
        }
        //还没有翻完小游戏卡牌，就去翻，花或者飞镖
        if (index == -1 && result.getPosition() != null && result.getPosition().size() != result.getSequen().size()) {
            throw new ManagedException(-1, "小游戏卡牌还未翻完！");
        }
        if (result.getPosition() != null && result.getPosition().contains(index)) {
            throw new ManagedException(-1, "重复选择了同一张牌");
        }
        boolean finish = result.select(index);
        if (!finish) {//小游戏还未结束
            return;
        }
        logger.debug("本次小游戏结束");
        try {
            //开始结算
//            dataService.settleMgame(playerService.getDeskPlayer(), configRoom.getGameId(), configRoom.getRoomId(), num,
//                    result);
            if (result.getType() == MType.DART) {//累记免费旋转次数
                playerService.setFreeCount(playerService.getFreeCount() + result.getScope());
            }
            playerService.change(result.getTotalFen());//更新玩家金币
        } catch (Exception e) {
            dataService.settleError(playerService.getDeskPlayer(), result, e);
            logger.error("小游戏结算失败！", e);
        }
        if (playerService.removeMgame()) {//清除本次小游戏数据 ，如果全部小游戏结束，进入正常游戏状态
            removeStatus(Status.MGAME);//移除小游戏状态
            playerService.sendMessage(SocketEnum.finishI, "");//服务器结算完成
        }
    }

    /**
     * 检查能否进行游戏
     * */
    private void canGame(PlayerService playerService, boolean free, int betIndex) {
        if (hasStatus(Status.ROTATE)) {//上一轮旋转还未结束
            throw new ManagedException(-1, "非法游戏");
        }
        if (hasStatus(Status.MGAME)) {
            throw new ManagedException(-1, "状态错误，小游戏还未完成！");
        }
        if (betIndex != -1 && !betIndexs.contains(betIndex)) {
            throw new ManagedException(-1, "非法投注");
        }
        if (free && betIndex == -1) {
            throw new ManagedException(-1, "免费中无法购买");
        }
        if (betIndex == -1 && playerService.getCombo() != miniCombo - 1) {
            throw new ManagedException(-1, "不满足购买条件");
        }
        long buyMoney = 0;//购买服务花费
        if (betIndex == -1) {
            int buyTimes = playerService.getBuyTimes();
            int buyLimit = configRoom.getBuy2Bet().keySet().stream().sorted((o1, o2) -> -1).findFirst().get();
            if (buyTimes >= buyLimit) {
                throw new ManagedException(-1, "购买上限");
            }
            betIndex = playerService.getBetIndex();
            //本次购买花费
            buyMoney = configRoom.getBuy2Bet().get(buyTimes + 1);
        }
        long totalBet = configRoom.getBetArea()[betIndex] * Rule.getSize() + buyMoney;
        if (!free && totalBet > playerService.getDeskPlayer().getMondey(PropType.GOLD)) {
            //throw new ManagedException(-1, "金币不足");
        }
    }

    /**玩家操作超时检测*/
    private Thread scheduler = new Thread(() -> {
        while (true) {
            try {
                synchronized (lock) {
                    if (this.isIdle()) {//桌子空闲
                        lock.wait();
                    }
                    long last = playerService.getLastUpdate().getTime();//最近一次旋转时间戳
                    long current = System.currentTimeMillis();
                    if ((current - last) >= timeout) {//超时
                        Channel channel = playerService.getChannel();
                        playerService.sendMessage(SocketEnum.infoI, TishiManager.Tishi_T_LongNoOperate);
                        gameService.exit(playerService);
                        if (channel != null) {
                            channel.attr(ServerHandler.PLAYER_KEY).remove();
                            channel.close();
                        }
                        logger.debug("长时间未操作，被系统踢出房间！");
                    } else {
                        //超时检查
                        logger.debug("超时检查:{}", timeout - (current - last));
                        lock.wait(timeout - (current - last));
                    }
                }
                //Thread.sleep(delay);
            } catch (Exception e) {
                logger.error("桌子[{}]超时检查线程发生未知错误！", num, e);
            }
        }
    });
}
